#!/usr/bin/python2.4
#
# Copyright 2010 Google Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Contains problem definitions.

Problem object plays the following roles:
     1) it defines the number of objectives and their names
     2) it connects the datasource object to other modules
     3) it evaluates a solution to itself using datasource

The algorithm is set to minimise all objectives. If an objective
should be maximised (e.g. coverage), the solution instance should
take the negative value of that objective so that minimisation
works.

A new formulation of the test optimisation should start from
subclassing Problem class. Override the number of objectives
and their names. The evaluate method should take a solution
instance and assign the objective values to the instance.
Note that the objective names are used throughout the entire
optimisation process; the pipeline_runner and optimizer uses
an objective named 'cost' so it should be always included.
The order of individual objectives do not matter.
"""

__author__ = 'ntrolls@gmail.com (Shin Yoo)'


import numpy


class Problem(object):
  def __init__(self, ds):
    self.datasource = ds
    self.numberoftests = self.datasource.numberoftests
    self.numberoftargets = self.datasource.numberoftargets


class Problem2Obj(Problem):
  """Maximises coverage and minimises cost."""

  numberofobjectives = 2
  objectivenames = ['cost', 'coverage']

  def Evaluate(self, solution):
    cost = 0
    trace = numpy.zeros(self.numberoftargets, dtype=bool)
    for i in xrange(len(solution.gene)):
      if solution.gene[i]:
        trace = trace.__or__(self.datasource.traces[i])
        cost += self.datasource.costs[i]
    solution.objectives[0] = float(cost)
    solution.objectives[1] = - float(len(trace.nonzero()[0])) / float(
        self.numberoftargets)


class Problem3Obj(Problem):
  """Maximises coverage and past fault coverage, minimises cost."""

  numberofobjectives = 3
  objectivenames = ['cost', 'coverage', 'past_fault_cov']

  def Evaluate(self, solution):
    """Evaluates a solution according to 3objective formulation."""
    cost = 0
    failed = 0
    trace = numpy.zeros(self.numberoftargets, dtype=bool)
    for i in xrange(len(solution.gene)):
      if solution.gene[i]:
        trace = trace.__or__(self.datasource.traces[i])
        cost += self.datasource.costs[i]
        failed += (self.datasource.executed[i] - self.datasource.passed[i]) > 0
    solution.objectives[0] = float(cost)
    solution.objectives[1] = - float(len(trace.nonzero()[0])) / float(
        self.numberoftargets)
    solution.objectives[2] = - float(failed) / float(self.numberoftargets)


class Problem4ObjMaxReturnTime(Problem):
  """Includes max_cost: the largest cpu_time of tests in the solution."""
  numberofobjectives = 4
  objectivenames = ['cost', 'coverage', 'past_fault_cov', 'max_cost']

  def Evaluate(self, solution):
    """Evaluates a solution according to 4objective formulation."""

    # first we initialise objective values
    cost = 0
    failed = 0
    # trace is an numpy.ndarray that contains accumulative coverage
    trace = numpy.zeros(self.numberoftargets, dtype=bool)
    # max_cost is the cost of an individual test that is the largest
    max_cost = 0

    # while iterating over the genotype (i.e. bitarray)
    for i in xrange(len(solution.gene)):
      if solution.gene[i]: # if the bit is set (i.e. corresponding test chosen)
        # update coverage
        trace = trace.__or__(self.datasource.traces[i])
        # add cost
        cost += self.datasource.costs[i]
        # count failing tests
        failed += (self.datasource.executed[i] - self.datasource.passed[i]) > 0
        # update max_cost
        if self.datasource.costs[i] > max_cost:
          max_cost = self.datasource.costs[i]

    # assign the objectives back to the solution that was being evaluated
    solution.objectives[0] = cost
    solution.objectives[1] = - float(len(trace.nonzero()[0])) / float(
        self.numberoftargets)
    solution.objectives[2] = - float(failed) / float(self.numberoftargets)
    solution.objectives[3] = max_cost
